package com.hww.common.util;

import android.annotation.SuppressLint;
import android.content.Context;
import android.os.Build;
import android.telephony.TelephonyManager;
import android.util.Log;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.lang.reflect.Method;

/**
 * @author hww
 * @date 2022/1/6/0006
 * email: heww@zhongyuntech.cn
 * description:自定义在android上获取唯一码对象
 */
public class AndroidUniqueCodeUtil {

    private AndroidUniqueCodeUtil() {
    }

    private static final AndroidUniqueCodeUtil instance = new AndroidUniqueCodeUtil();

    public static AndroidUniqueCodeUtil getInstance() {
        return instance;
    }

    /**
     * 获取设备序列号唯一码
     *
     * @return
     */
    public String getSerial(Context context) {
        StringBuilder stringBuilder = new StringBuilder();
        String serial = "";

        try {
            String manufacturer = Build.MANUFACTURER;
            stringBuilder.append("型号:").append(manufacturer).append("\n");

            /*优博讯设备，首先通过反射调用优博讯提供的sdk获取序列号，若失败在通过指令方式获取*/
            serial = ybxReflectGetDeviceId();
            stringBuilder.append("ybx reflect : ").append(serial).append("\n");

            if ("Urovo".equals(manufacturer) || "UBX".equals(manufacturer)) {
                if (null == serial || serial.length() == 0) {
                    try {
                        /*通过shell方式获取优博讯设备上 getprop persist.sys.device.sn 字段的内容*/
                        serial = execCommand("getprop persist.sys.device.sn");
                        stringBuilder.append("ybx  getprop persist.sys.device.sn : ").append(serial).append("\n");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }

            if (null == serial || serial.length() == 0) {
                /*针对东集设备进行获取SN*/
                serial = djkjReflectGetSN();
                stringBuilder.append("djkj reflect : ").append(serial).append("\n");
            }

            if (null == serial || serial.length() == 0) {
                if ("ZCS".equals(manufacturer)) {
                    /*针对已经出货的神盾设备，兼容使用序列号的方式授权点度在线身份证模块，其他的优先采用imei方式*/
                    serial = execCommand("getprop ro.serialno");
                    stringBuilder.append("getprop ro.serialno : ").append(serial).append("\n");
                } else {
                    serial = getImei(context);
                    stringBuilder.append("imei : ").append(serial).append("\n");
                }
            }

            if (null == serial || serial.length() == 0) {
                /*通用的获取序列号方式 getprop ro.serialno */
                serial = execCommand("getprop ro.serialno");
                stringBuilder.append("getprop ro.serialno : ").append(serial).append("\n");
            }
        } catch (Exception e) {
            e.printStackTrace();
            stringBuilder.append("exception : ").append(Log.getStackTraceString(e)).append("\n");
        }
        System.out.println(AndroidUniqueCodeUtil.class.getName() + "   " + stringBuilder.toString());
        return serial;
    }

    @SuppressLint("MissingPermission")
    private String getImei(Context context) {
        try {
            TelephonyManager tm = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
            if (tm != null) {
                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
                    return tm.getImei();
                } else {
                    return tm.getDeviceId();
                }
            }
        } catch (SecurityException e) {
            e.getMessage();
        } catch (Exception e) {
            e.getMessage();
        }
        return "";
    }


    /**
     * 获取设备的mac ，若版本低于的android9.0直接使用系统的mac地址，高于则使用序列号计算MD5并截取最后的12位作为mac
     *
     * @return mac地址
     */
    public String getMac(Context context) {
        String mac = "";
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("targetApi = ").append(Build.VERSION.SDK_INT).append("\n");
        if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) {
            /*当前设备大于android9.0(api=28)使用序列号方式*/
            String serial = getSerial(context);
            /*deviceBean中序列号字段长度必须小于等于32,通过计算md5值确保32位长度*/
            String md5Serial = MD5Utils.getMD5String(serial.getBytes());
            String md5String = MD5Utils.getMD5String(md5Serial.getBytes());
            int md5Len = md5String.length();
            /*deviceBean中mac字段长度必须小于等于12*/
            mac = (md5Len > 12 ? md5String.substring(md5Len - 12) : md5String);
            stringBuilder.append("serial : ").append(serial).append("\n");
        } else {
            mac = MacUtil.getMAC();
            mac = mac.replace(":", "");
        }
        stringBuilder.append("mac : ").append(mac).append("\n");
        System.out.println("common.UniqueCodeUtil getMac:" + stringBuilder.toString());
        /*saveProcess("getMac", stringBuilder.toString());*/
        return mac;
    }


    /**
     * 优博讯设备反射通过getDeviceId方法获取sn
     *
     * @return
     */
    private String ybxReflectGetDeviceId() {
        /*String manufacturer = Build.MANUFACTURER;*/
//        if ("Urovo".equals(manufacturer) || "UBX".equals(manufacturer)) {
            /*已知的两个优博讯设备产品的名字*/
            try {
                /*获取Class对象：注意此字符串必须是真实路径，就是带包名的类路径，包名.类名*/
                Class<?> deviceManager = Class.forName("android.device.DeviceManager");
                /*newInstance是创建了一个构造方法的声明类的新实例对象*/
                Object object = deviceManager.getConstructor().newInstance();
                /*调用指定方法（所有包括私有的），需要传入两个参数，第一个是调用的方法名称，第二个是方法的形参类型，切记是类型*/
                Method getDeviceId = deviceManager.getDeclaredMethod("getDeviceId");
                /*暴力反射，解除私有限定(忽略访问修饰符)*/
                getDeviceId.setAccessible(true);
                /*invoke() 需要两个参数，一个是要调用的对象（获取有反射），一个是实参(无参不需要填)*/
                Object invoke = getDeviceId.invoke(object);
                if (null != invoke) {
                    return String.valueOf(invoke);
                }
            } catch (Exception e) {
                /*e.printStackTrace();*/
            }
        return "";
    }

    /**
     * 东集科技手持机设备获取SN方法
     *
     * @return
     */
    private String djkjReflectGetSN() {
        try {
            /*获取Class对象：注意此字符串必须是真实路径，就是带包名的类路径，包名.类名*/
            Class<?> misc = Class.forName("com.seuic.misc.Misc");
            /*newInstance是创建了一个构造方法的声明类的新实例对象*/
            Object object = misc.getConstructor().newInstance();
            /*调用指定方法（所有包括私有的），需要传入两个参数，第一个是调用的方法名称，第二个是方法的形参类型，切记是类型*/
            Method getDeviceId = misc.getDeclaredMethod("getSN");
            /*暴力反射，解除私有限定(忽略访问修饰符)*/
            getDeviceId.setAccessible(true);
            /*invoke() 需要两个参数，一个是要调用的对象（获取有反射），一个是实参(无参不需要填)*/
            Object invoke = getDeviceId.invoke(object);
            if (null != invoke) {
                return String.valueOf(invoke);
            }
        } catch (Exception e) {
            /*e.printStackTrace();*/
        }
        return "";
    }


    /**
     * 通过shell指令查询指定内容
     *
     * @param command 查询指令
     * @return 查询内容
     */
    private String execCommand(String command) {
        return execCommand(command, false);
    }

    private String execCommand(String command, boolean isRoot) {
        return execCommand(new String[]{command}, isRoot, true);
    }

    /**
     * execute shell commands
     *
     * @param commands        command array
     * @param isRoot          whether need to run with root
     * @param isNeedResultMsg whether need result msg
     * @return <ul>
     * </ul>
     */
    private String execCommand(String[] commands, boolean isRoot, boolean isNeedResultMsg) {
        int result = -1;
        if (commands == null || commands.length == 0) {
            return "";
        }

        Process process = null;
        BufferedReader successResult = null;
        BufferedReader errorResult = null;
        StringBuilder successMsg = null;
        StringBuilder errorMsg = null;

        DataOutputStream os = null;
        try {
            process = Runtime.getRuntime().exec(isRoot ? "su" : "sh");
            os = new DataOutputStream(process.getOutputStream());
            for (String command : commands) {
                if (command == null) {
                    continue;
                }

                // donnot use os.writeBytes(commmand), avoid chinese charset error
                os.write(command.getBytes());
                os.writeBytes("\n");
                os.flush();
            }
            os.writeBytes("exit\n");
            os.flush();

            result = process.waitFor();
            // get command result
            if (isNeedResultMsg) {
                successMsg = new StringBuilder();
                errorMsg = new StringBuilder();
                successResult = new BufferedReader(new InputStreamReader(process.getInputStream()));
                errorResult = new BufferedReader(new InputStreamReader(process.getErrorStream()));
                String s;
                while ((s = successResult.readLine()) != null) {
                    successMsg.append(s);
                }
                while ((s = errorResult.readLine()) != null) {
                    errorMsg.append(s);
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                if (os != null) {
                    os.close();
                }
                if (successResult != null) {
                    successResult.close();
                }
                if (errorResult != null) {
                    errorResult.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }

            if (process != null) {
                process.destroy();
            }
        }
        System.out.println("execCommand errorMsg:" + errorMsg);
        System.out.println("execCommand successMsg Size:" + result);
        System.out.println("execCommand successMsg:" + successMsg);
        return null == successMsg ? "" : successMsg.toString();
    }
}
